Micron Document
🎖️GitЯра🎖️

Commit 9d3e3a1dfdd24ef5cc107542397aa1e3060c84d6


Parents : 5bdbd33
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-07-10T12:02:36-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-07-10T17:02:36Z

fix(firmware): never offer an alpha release older than current stable (#6198)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

Changes
Diff

diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepositoryImpl.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepositoryImpl.kt
index da3b53e35c..287f2fc2bb 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepositoryImpl.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepositoryImpl.kt
@@ -75,7 +75,8 @@ open class FirmwareReleaseRepositoryImpl(
private fun getLatestFirmware(releaseType: FirmwareReleaseType): Flow<FirmwareRelease?> = staleWhileRevalidateFlow(
loadFromCache = {
ensureSeeded()
- localDataSource.getLatestRelease(releaseType)?.asExternalModel()
+ val latest = localDataSource.getLatestRelease(releaseType)?.asExternalModel()
+ if (releaseType == FirmwareReleaseType.ALPHA) latest.notBelowStable() else latest
},
shouldFetch = { cached ->
cached == null || localDataSource.getLatestRelease(releaseType)?.isStale() != false
@@ -93,6 +94,17 @@ open class FirmwareReleaseRepositoryImpl(
localDataSource.deleteAllFirmwareReleases()
}
+ /**
+ * After a stable promotion the alpha channel lags behind stable — never offer a downgrade. Reads the cached stable
+ * row (every refresh writes both types together, so it is as fresh as the alpha row) rather than combining with
+ * [stableRelease], which would run a second revalidate pipeline — and potentially a duplicate network refresh — for
+ * every alpha collector.
+ */
+ private suspend fun FirmwareRelease?.notBelowStable(): FirmwareRelease? {
+ val stable = localDataSource.getLatestRelease(FirmwareReleaseType.STABLE)?.asExternalModel()
+ return if (this != null && stable != null && asDeviceVersion() < stable.asDeviceVersion()) stable else this
+ }
+
/**
* Applies the bundled snapshot per release type whenever it is newer than what is cached for that type — not just
* when the cache is empty. The bundle is refreshed weekly in CI, so an app update carries fresh data even for users

diff --git a/core/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepositoryImplTest.kt b/core/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepositoryImplTest.kt
index 393755b85a..5b9cc0b54c 100644
--- a/core/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepositoryImplTest.kt
+++ b/core/data/src/jvmTest/kotlin/org/meshtastic/core/data/repository/FirmwareReleaseRepositoryImplTest.kt
@@ -228,6 +228,18 @@ class FirmwareReleaseRepositoryImplTest {
)
}
+ @Test
+ fun alphaChannelNeverOffersVersionBelowStable() = runBlocking {
+ // After a promotion the newest alpha (2.7.25) is older than stable (2.7.26) — offer stable instead.
+ dao.insert(FirmwareReleaseEntity(id = "v2.7.26.54e0d8d", releaseType = FirmwareReleaseType.STABLE))
+ dao.insert(FirmwareReleaseEntity(id = "v2.7.25.104df5f", releaseType = FirmwareReleaseType.ALPHA))
+ assertEquals("v2.7.26.54e0d8d", repository.alphaRelease.toList().last()?.id)
+
+ // A genuinely newer alpha is still offered.
+ dao.insert(FirmwareReleaseEntity(id = "v2.7.27.abc1234", releaseType = FirmwareReleaseType.ALPHA))
+ assertEquals("v2.7.27.abc1234", repository.alphaRelease.toList().last()?.id)
+ }
+
@Test
fun olderBundledSnapshotNeverRegressesCache() = runBlocking {
// A successful network refresh left the cache newer than the (weekly) bundle.

Served by rngit 1.5.2 - Generated in 0.1s